package com.che.software.testato.business;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.che.software.testato.business.exception.IntentionSearchManagerException;
import com.che.software.testato.business.exception.ItemSearchManagerException;
import com.che.software.testato.business.exception.VariantSearchManagerException;
import com.che.software.testato.domain.dao.IIntentionDAO;
import com.che.software.testato.domain.dao.IItemDAO;
import com.che.software.testato.domain.dao.IVariantDAO;
import com.che.software.testato.domain.dao.jdbc.exception.IntentionSearchDAOException;
import com.che.software.testato.domain.dao.jdbc.exception.ItemSearchDAOException;
import com.che.software.testato.domain.dao.jdbc.exception.VariantSearchDAOException;
import com.che.software.testato.domain.entity.Counter;
import com.che.software.testato.domain.entity.Intention;
import com.che.software.testato.domain.entity.Item;
import com.che.software.testato.domain.entity.MapArrow;
import com.che.software.testato.domain.entity.creation.ScriptCreation;
import com.che.software.testato.domain.entity.search.IntentionSearch;
import com.che.software.testato.domain.entity.search.ItemSearch;
import com.che.software.testato.domain.entity.search.VariantSearch;

import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.graph.util.EdgeType;

/**
 * Manager dedicated to the map diagrams.
 * 
 * @author Clement HELIOU (clement.heliou@che-software.com).
 * @copyright Che Software.
 * @license GNU General Public License.
 * @since July, 2011.
 * 
 *        This file is part of Testato.
 * 
 *        Testato is free software: you can redistribute it and/or modify it
 *        under the terms of the GNU General Public License as published by the
 *        Free Software Foundation, either version 3 of the License, or (at your
 *        option) any later version.
 * 
 *        Testato is distributed in the hope that it will be useful, but WITHOUT
 *        ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *        FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *        for more details.
 * 
 *        You should have received a copy of the GNU General Public License
 *        along with Testato. If not, see <http://www.gnu.org/licenses/>.
 * 
 *        Testato's logo is a creation of Arrioch
 *        (http://arrioch.deviantart.com/) and it's distributed under the terms
 *        of the Creative Commons License.
 */
@Service("mapDiagramManager")
public class MapDiagramManager {

	/**
	 * Constants.
	 */
	private static final Logger LOGGER = Logger.getLogger(MapDiagramManager.class);

	/**
	 * Members.
	 */
	@Autowired
	private IIntentionDAO intentionDAO;
	@Autowired
	private IItemDAO itemDAO;
	@Autowired
	private IVariantDAO variantDAO;

	/**
	 * Creates a map diagram from intentions and items lists.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param intentions the intentions list.
	 * @param items the items list.
	 * @return the resulting map diagram.
	 * @since July, 2011.
	 */
	public DirectedGraph<Intention, MapArrow> createMapDiagramFromIntentionsAndItems(List<Intention> intentions, List<Item> items) {
		LOGGER.debug("createMapDiagramFromIntentionsAndItems(" + intentions.size() + " intentions, " + items.size() + " items).");
		DirectedGraph<Intention, MapArrow> mapDiagram = new DirectedSparseMultigraph<Intention, MapArrow>();
		for (Item item : items) {
			mapDiagram.addEdge(new MapArrow(item.isOperationalized(), item.isRefined(), item.getLabel(), item.getMaxIterationNumber(), item.getTestCaseId(), item.getActionPlanId(), item.getSourceIntentionLabel(), item.getTargetIntentionLabel()), getIntentionFromList(intentions, item.getSourceIntentionId()), getIntentionFromList(intentions, item.getTargetIntentionId()), EdgeType.DIRECTED);
		}
		LOGGER.debug(mapDiagram);
		return mapDiagram;
	}

	/**
	 * Elicitates all the scripts from a given hierarchy.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram representing the high level of the
	 *        hierarchy to elicitate.
	 * @return the resulting objects list.
	 * @since July, 2011.
	 * @throws VariantSearchManagerException if an error occurs during a variant
	 *         search.
	 * @throws IntentionSearchManagerException if an error occurs during an
	 *         intention search.
	 * @throws ItemSearchManagerException if an error occurs during an item
	 */
	public List<ScriptCreation> elicitateScriptsFromHierarchy(DirectedGraph<Intention, MapArrow> mapDiagram) throws VariantSearchManagerException, IntentionSearchManagerException, ItemSearchManagerException {
		LOGGER.debug("elicitateScriptsFromHierarchy(" + mapDiagram + ").");
		List<ScriptCreation> allHierarchyScripts = new ArrayList<ScriptCreation>();
		elicitateMapScripts(mapDiagram, allHierarchyScripts);
		return allHierarchyScripts;
	}

	/**
	 * Recursive method used to discover all the scripts contained in a map.
	 * First, the method discovers all paths contained in the map diagram. Then,
	 * all the lower test cases and paths of refined maps are discovered by a
	 * recursive call. Finally, the scripts are updated and cloned with theses
	 * results.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram representing the level to elicitate.
	 * @param allMapScripts the accumulator for the discovered scripts.
	 * @since July, 2011.
	 * @throws VariantSearchManagerException if an error occurs during a variant
	 *         search.
	 * @throws IntentionSearchManagerException if an error occurs during an
	 *         intention search.
	 * @throws ItemSearchManagerException if an error occurs during an item
	 *         search.
	 */
	private void elicitateMapScripts(DirectedGraph<Intention, MapArrow> mapDiagram, List<ScriptCreation> allMapScripts) throws VariantSearchManagerException, IntentionSearchManagerException, ItemSearchManagerException {
		LOGGER.debug("elicitateMapScripts(" + mapDiagram + ").");
		getMapScript(mapDiagram, getStartIntentionFromMapDiagram(mapDiagram), null, allMapScripts, null);
		LOGGER.debug(allMapScripts.size() + " scripts discovered for this map.");
		List<MapArrow> lowerArrows = new ArrayList<MapArrow>(mapDiagram.getEdges());
		for (MapArrow arrow : lowerArrows) {
			if (arrow.isOperationalized()) { // Test case.
				LOGGER.debug("Variants research for test case n° " + arrow.getTestCaseId() + ".");
				try {
					arrow.setVariants(variantDAO.searchVariant(new VariantSearch(arrow.getTestCaseId())));
				} catch (VariantSearchDAOException e) {
					throw new VariantSearchManagerException(e);
				}
			} else { // Refined map.
				LOGGER.debug("Refined map scripts research for action plan n° " + arrow.getActionPlanId() + ".");
				List<ScriptCreation> allRefinedMapScripts = new ArrayList<ScriptCreation>();
				try {
					elicitateMapScripts(createMapDiagramFromIntentionsAndItems(intentionDAO.searchIntention(new IntentionSearch(arrow.getActionPlanId())), itemDAO.searchItem(new ItemSearch(arrow.getActionPlanId()))), allRefinedMapScripts);
					arrow.setScripts(allRefinedMapScripts);
				} catch (IntentionSearchDAOException e) {
					throw new IntentionSearchManagerException(e);
				} catch (ItemSearchDAOException e) {
					throw new ItemSearchManagerException(e);
				}
			}
		}
		for (ScriptCreation script : allMapScripts) {
			for (MapArrow arrow : script.getScriptArrows()) {
				arrow.setScripts(getArrowFromList(lowerArrows, arrow.getLabel()).getScripts());
			}
		}
	}

	/**
	 * Retrieves an arrow contained in a list from his label.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param arrows the arrows list.
	 * @param arrowLabel the arrow to retrieve label.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	private MapArrow getArrowFromList(List<MapArrow> arrows, String arrowLabel) {
		LOGGER.debug("getArrowFromList(" + arrowLabel + ").");
		for (MapArrow arrow : arrows) {
			if (arrow.getLabel().equalsIgnoreCase(arrowLabel)) {
				return arrow;
			}
		}
		return null;
	}

	/**
	 * Retrieves an intention contained in a list from his id.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param intentions the intentions list.
	 * @param intentionId the intention to retrieve id.
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	private Intention getIntentionFromList(List<Intention> intentions, int intentionId) {
		LOGGER.debug("getIntentionFromList(" + intentionId + ").");
		for (Intention intention : intentions) {
			if (intentionId == intention.getIntentionId()) {
				return intention;
			}
		}
		return null;
	}

	/**
	 * Recursive method used to discover paths in a map diagram. First, detects
	 * the type of the current intention, then adds it in the accumulator if
	 * necessary. Finally, clones current script in terms of the current
	 * intention successors.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram representing the level to elicitate.
	 * @param currentIntention the current intention to evaluate.
	 * @param currentScript the current script being built.
	 * @param accumulator the accumulator of built scripts.
	 * @param counter the counter to use for the return arrows.
	 * @since July, 2011.
	 */
	private void getMapScript(DirectedGraph<Intention, MapArrow> mapDiagram, Intention currentIntention, ScriptCreation currentScript, List<ScriptCreation> accumulator, Map<String, Counter> counter) {
		LOGGER.debug("getMapScript(" + currentIntention.getLabel() + ", " + accumulator.size() + " scripts already created.).");
		if (null == currentScript) {
			ScriptCreation newScript = new ScriptCreation();
			getMapScript(mapDiagram, currentIntention, newScript, accumulator, counter);
		} else {
			currentScript.addIntention(currentIntention);
			if (0 == mapDiagram.getSuccessorCount(currentIntention)) {
				LOGGER.debug("End of the script discovery (" + currentScript.getScriptIntentions().size() + " intentions, " + currentScript.getScriptArrows() + " arrows).");
				accumulator.add(currentScript);
			} else { // Not the starting or ending intention.
				List<MapArrow> successors = new ArrayList<MapArrow>(mapDiagram.getOutEdges(currentIntention));
				LOGGER.debug(successors.size() + " successors...");
				for (int i = 1; i < successors.size(); i++) {

					// Current script is duplicated n-1 times.
					processIntentionSuccessor(mapDiagram, currentIntention, successors.get(i), new ScriptCreation(currentScript), counter, accumulator);
				}
				processIntentionSuccessor(mapDiagram, currentIntention, successors.get(0), currentScript, counter, accumulator);
			}
		}
	}

	/**
	 * Returns the beginning intention object of a map diagram.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram to look in.
	 * @return the beginning intention object of the map.
	 * @since July, 2011.
	 */
	private Intention getStartIntentionFromMapDiagram(DirectedGraph<Intention, MapArrow> mapDiagram) {
		LOGGER.debug("getStartIntentionFromMapDiagram(" + mapDiagram + ").");
		for (Intention intention : new ArrayList<Intention>(mapDiagram.getVertices())) {
			if (0 == mapDiagram.getPredecessorCount(intention)) {
				return intention;
			}
		}
		return null; // Doesn't happened.
	}

	/**
	 * Process the current script in terms of a current intention successor.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param mapDiagram the map diagram representing the level being evaluate.
	 * @param currentIntention the current intention.
	 * @param followingArrow the following arrow to evaluate.
	 * @param currentScript the script being built.
	 * @param counter the counter to use for the return arrows.
	 * @param accumulator the accumulator of built scripts.
	 * @since July, 2011.
	 */
	private void processIntentionSuccessor(DirectedGraph<Intention, MapArrow> mapDiagram, Intention currentIntention, MapArrow followingArrow, ScriptCreation currentScript, Map<String, Counter> counter, List<ScriptCreation> accumulator) {
		LOGGER.debug("processIntentionSuccessor(" + currentIntention.getLabel() + "," + followingArrow.getLabel() + ").");
		if (!currentIntention.getLabel().equalsIgnoreCase(mapDiagram.getDest(followingArrow).getLabel())) {
			if (null != followingArrow.getMaxIterationNumber() && 0 != followingArrow.getMaxIterationNumber()) {
				LOGGER.debug("Reverse section (" + followingArrow.getLabel() + "," + followingArrow.getMaxIterationNumber() + ").");
				if (null == counter) {
					Map<String, Counter> newCounter = new HashMap<String, Counter>();
					newCounter.put(followingArrow.getLabel(), new Counter(1, followingArrow.getMaxIterationNumber()));
					currentScript.addArrow(followingArrow);
					getMapScript(mapDiagram, mapDiagram.getDest(followingArrow), currentScript, accumulator, newCounter);
				} else {
					Counter currentCounter = counter.get(followingArrow.getLabel());
					if (null == currentCounter) {
						counter.put(followingArrow.getLabel(), new Counter(1, followingArrow.getMaxIterationNumber()));
					} else {
						if (currentCounter.getMaxIterationNumber() != currentCounter.getCurrentIterationNumber()) {
							currentCounter.setCurrentIterationNumber(currentCounter.getCurrentIterationNumber() + 1);
							counter.put(followingArrow.getLabel(), currentCounter);
						}
					}
					currentScript.addArrow(followingArrow);
					getMapScript(mapDiagram, mapDiagram.getDest(followingArrow), currentScript, accumulator, counter);
				}
			} else {
				LOGGER.debug("Classical section (" + followingArrow.getLabel() + ").");
				currentScript.addArrow(followingArrow);
				getMapScript(mapDiagram, mapDiagram.getDest(followingArrow), currentScript, accumulator, counter);
			}
		} else {
			LOGGER.debug("Recursive section (" + followingArrow.getLabel() + "," + followingArrow.getMaxIterationNumber() + ").");
			for (int j = 0; j < followingArrow.getMaxIterationNumber(); j++) {
				ScriptCreation recursiveClone = new ScriptCreation(currentScript);
				for (int k = j; k < followingArrow.getMaxIterationNumber(); k++) {
					recursiveClone.addArrow(followingArrow);
				}
				List<MapArrow> successors = new ArrayList<MapArrow>(mapDiagram.getOutEdges(currentIntention));
				for (int i = 1; i < successors.size(); i++) {
					if (!mapDiagram.getDest(successors.get(i)).getLabel().equalsIgnoreCase(currentIntention.getLabel())) {
						processIntentionSuccessor(mapDiagram, currentIntention, successors.get(i), new ScriptCreation(recursiveClone), counter, accumulator);
					}
				}
				if (!mapDiagram.getDest(successors.get(0)).getLabel().equalsIgnoreCase(currentIntention.getLabel())) {
					processIntentionSuccessor(mapDiagram, currentIntention, successors.get(0), recursiveClone, counter, accumulator);
				}
			}
		}
	}

	/**
	 * Getter for the private field value variantDAO.
	 * 
	 * @return the variantDAO field value.
	 */
	public IVariantDAO getVariantDAO() {
		return variantDAO;
	}

	/**
	 * Setting a value to the variantDAO field.
	 * 
	 * @param variantDAO the value to set.
	 */
	public void setVariantDAO(IVariantDAO variantDAO) {
		this.variantDAO = variantDAO;
	}

	/**
	 * Getter for the private field value intentionDAO.
	 * 
	 * @return the intentionDAO field value.
	 */
	public IIntentionDAO getIntentionDAO() {
		return intentionDAO;
	}

	/**
	 * Setting a value to the intentionDAO field.
	 * 
	 * @param intentionDAO the value to set.
	 */
	public void setIntentionDAO(IIntentionDAO intentionDAO) {
		this.intentionDAO = intentionDAO;
	}

	/**
	 * Getter for the private field value itemDAO.
	 * 
	 * @return the itemDAO field value.
	 */
	public IItemDAO getItemDAO() {
		return itemDAO;
	}

	/**
	 * Setting a value to the itemDAO field.
	 * 
	 * @param itemDAO the value to set.
	 */
	public void setItemDAO(IItemDAO itemDAO) {
		this.itemDAO = itemDAO;
	}
}